﻿// Jquery .BindTool 2.0
// 日期:2009-12-31
// 网址:http://www.cnblogs.com/fantasylu
// 描述:把json对象，按照一定的规则赋值给HTML控件，并从HTML控件中获取数据。支持模板及模板切换。
//      以及自定义绑定方式。    
// 1.1 增加了无配置的特性，根据json对象自动对应需要的html控件。
// 1.1.3 改变bind 的返回值，返回false中止绑定，返回object，就用来绑定。
// 1.1.4 增加对optiong.bindWay[i].bindFrom 为false的判定，如果为false，collect的时候不会收集改属性。默认为true
// 1.2 在template的模式下，能够为已经存在的模板实例重新赋值。
// 1.5 提供强类型的转换，之前的版本collect的json对象全部都是字符串. 并且提供BindWay的事件
// 1.5.1 
//    1)把Bool表达式，改为json对象。减少判断代码
//    2)更正没有type属性的情况下 出错问题。bindWay type现在是可选属性
//    3)使用jquery 原生方法绑定HTML select 方法，减少10行代码。现在在Windows下看 min的文件是7kb，看上去爽一点。
//    4)修复不能从Select中获取已选定的值
// 2.0 增加remoteBindTool，支持class查找，增加了 onBound和onBind事件

/// <reference path="jquery.min.js" />

(function($) {
    $.fn.bindTool = function() {
        var args = arguments;
        if (typeof args[0] == "string") {
            var bind = $(this).data("_bt");
            switch (args[0]) {
                case "collect":
                    var rData = [];
                    var searchRang;
                    if (bind.inTemp()) {
                        searchRang = $.makeArray(args[1] != undefined ? args[1] : this.children("[template='template']"));
                        if (searchRang.length == 0) {
                            return rData;
                        }
                    }
                    else {
                        searchRang = $.makeArray(this);
                        rData[0] = args[1] == undefined ? {} : args[1];
                    }
                    bind.collect(searchRang, rData);
                    return !bind.inTemp() || (args[1] != undefined && args[1].size() == 1) ? rData[0] : rData;
                case "bind":
                    //sample:$("#aa").bindTool("bind",jsonObject);
                    if (!bind.opts.bindWay) {
                        bind.opts.bindWay = _2BindWay($.isArray(args[1]) ? args[1][0] : args[1], bind);
                    }
                    return bind.bind(args[1], args[2] ? args[2] : $(this));
                case "switch":
                    //sample:$("con").bindTool("switch", "editTemplate",beSwitchArea);
                    return bind.switchTo(args[1], $(args[2]));
            }
        } else {
            // 初始化设定
            var opts = $.extend({}, $.fn.bindTool.defaults, args[0]);
            if (opts.idPrefix == false) {
                opts.idPrefix = _genId(this);
            }
            if (opts.template != false) {
                $(opts.template).hide();
            }
            this.data("_bt", new _bindTool(opts));
        }
        return $(this);
    };

    $.fn.bindTool.defaults = {
        bindWay: false, //[ {dataMember: "对象的成员，支持property.property的方式", bindTo: 绑定控件的方式
        //，可以是html id，或者jquery对象，或者"id.attrbuteName", bindFrom: 返回的数据对象}]
        template: false, //bind的对象是否为template，默认为false
        bindToContent: false, //template完成数据绑定,加入content的方法;
        bind: false, //绑定之前的事件,无论是switch 还是bind 都会引发这个事件,function(jsonObject,bindingTemplate):return true,continue;
        bound: false, //绑定后的事件,无论是switch还是bind都会引发这个事件,function(jsonObject,bindingTemplate)
        idPrefix: false,
        show: false, // function(beArea),当template,bind已经完成，准备显示的时候
        boolExp: { "0": false, "1": "true", "true": true, "false": false }, //boolean 的表达式。
        datePatten: /^\/Date\(\d*\)\/$/, //特殊的日期判定，其中这个是.net的日期格式判定
        dateCover: function(val) { //当符合datePatten的标准时，就是使用这个转换为日期型。date转换优于其他转换            
            return eval("new " + val.substr(1, val.length - 2));
        },
        dateFormat: function(dateVal) { //在Html呈现的时候，使用的格式 默认是2009-10-10
            return dateVal.getFullYear() + "-" + (dateVal.getMonth() + 1) + "-" + dateVal.getDate();
        },
        formatToDate: function(strValue) {
            var stAr = strValue.split('-');
            return new Date(parseInt(stAr[0]), (parseInt(stAr[1]) - 1), parseInt(stAr[2]));
        },
        onBind: false, //这个事件与bind区别是，当绑定是多个对象的时候，bind是每个对象都会触发一次，而这个触发一次。
        onBound: false//同上，
    };


    _bindTool = function(opts) {
        this.tmpIdx = 0;
        this.opts = opts;
        var constDefaultTemplateName = "template";
        var ins = this;
        $.each(opts.bindWay, function() { this.bindTool = ins; });

        //绑定数据到 opts制定的控件上面
        this.bind = function(jsonObject, srang) {
            var ins = this;
            var result = false;
            fireEvent(srang, ins.opts.onBind, jsonObject);
            $($.makeArray(jsonObject)).each(function() {
                if (ins.inTemp() && srang.attr(constDefaultTemplateName) == undefined) {
                    var temp = bindTo(this, ins, ins.opts.template, ins.opts, ins.tmpIdx);
                    if (temp != null) {
                        temp.attr(constDefaultTemplateName, constDefaultTemplateName);
                        $.isFunction(opts.bindToContent) ? opts.bindToContent(temp, srang) : temp.appendTo(srang);
                        ins.show(opts, temp);
                        ins.tmpIdx++;
                        result = true;
                    }
                }
                else {
                    result = bindTo(this, ins, srang, ins.opts) != null;
                }
            });
            fireEvent(srang, ins.opts.onBound, jsonObject);
            return result;
        };

        this.switchTo = function(switchTemplateName, switchArea) {
            var targetOpts = switchTemplateName == constDefaultTemplateName ? this.opts : this.opts[switchTemplateName];

            if (targetOpts.bindWay == undefined) {
                targetOpts.bindWay = this.opts.bindWay;
            }
            if (targetOpts.idPrefix == undefined) {
                targetOpts.idPrefix = _genId(targetOpts.template);
            }
            if (targetOpts.show == undefined) {
                targetOpts.show = this.opts.show;
            }

            var jsnObj = {};
            this.collectObj(this.opts, jsnObj, switchArea, this);

            var bindTemp = bindTo(jsnObj, this, targetOpts.template, targetOpts, parseInt(switchArea.attr("id").split("_")[1]));
            if (bindTemp != null) {
                switchArea.replaceWith(bindTemp);
                this.show(targetOpts, bindTemp);
                return bindTemp.attr(constDefaultTemplateName, switchTemplateName);
            }
        };
        this.collect = function(searchRang, jsnObj) {
            //templateAataArea 只有在template才有可有值，他必须是一个Jquery对象，代表
            //template show的时候所在的范围。
            //template bound
            var ins = this;
            $.each(searchRang, function(i) {
                var forUndefined = jsnObj[i] == undefined
                var jobj = forUndefined ? {} : jsnObj[i];
                ins.collectObj(ins.opts, jobj, $(this));
                forUndefined ? jsnObj.push(jobj) : jsnObj[i] = jobj;
            });

        };
        this.inTemp = function() {
            return this.opts.template != false;
        };

        this.show = function(opts, temp) {
            $.isFunction(opts.show) ? opts.show(temp) : temp.show();
        }

        this.collectObj = function(opts, jsnObj, searchRang) {
            var temp = searchRang.attr(constDefaultTemplateName);
            var opt = temp == constDefaultTemplateName || temp == undefined ? opts : opts[temp];
            var ins = this;
            $.each(opt.bindWay, function() {
                if ((this.dataMember == undefined && !($.isFunction(this.bindFrom))) || this.bindFrom === false) {
                    //dataMember没哟值，并且bindFrom不是Function，那么是无法获得创建Json对象的。
                    //又或者bindFrom==false，那么就等于Json对象是不需要收集.
                    return true;
                }
                var binder = getBinder(this, searchRang, opts.template != false, ins);
                if (binder != null && binder.getData != null) {
                    var data = binder.getData(jsnObj, this.dataMember);
                }
            });
        }
    };
    //static
    bindTo = function(jObj, ins, srang, opts, idx) {
        var prefix;
        var isTmpIns = srang.attr("template") != undefined;
        var changeId = ins.inTemp() && !isTmpIns;
        if (changeId) {
            srang = srang.clone().attr("id", srang.attr("id") + "_" + idx);
            prefix = opts.idPrefix + "_" + idx + "_";
        }

        var o = fireEvent(srang, opts.bind, jObj); //opts.bind(jObj, srang);
        if (o === false) {
            return null;
        } else if (typeof o == "object") {
            jObj = o;
        }

        $.each(opts.bindWay, function() {
            var binder = getBinder(this, srang, isTmpIns, ins);
            if (binder != null) {
                var v = this.dataMember != undefined ? gv(jObj, this.dataMember) : null;
                if (this.type == undefined) {
                    this.type = getValType(v, ins);
                }
                if (this.type == "date") {
                    var a = s2v(v, this); //转换非javascript Date类型 为Date类型，如把"/\Date(323213)/"转换为Date类型
                    sv(jObj, this.dataMember, a); //更改Json本来的类型。
                    v = ins.opts.dateFormat(a); //设定输出格式。
                }

                var bindInfo = { value: v, type: this.type, obj: jObj };
                fireEvent(binder.target, this.bind, bindInfo);

                binder.setData(jObj, bindInfo.value);

                if (changeId) {
                    var srcId = binder.target.attr("id");
                    binder.target.attr("srcid", srcId).attr("id", prefix + srcId);
                }
                fireEvent(binder.target, this.bound, bindInfo);
            }
        });
        fireEvent(srang, opts.bound, jObj);

        return srang;
    };

    function gv(o, e) {
        return eval("o." + e);
    }
    function sv(j, e, v) {
        eval("j." + e + "=v");
    }

    getValType = function(obj, ins) {
        var o = typeof obj;
        if (isDate(obj, ins)) {
            return "date";
        }
        if (o == "object" && $.isArray(obj)) {
            var childType = "string";
            if (obj.length != 0) {
                childType = getValType(obj[0], ins);
                return "array-" + childType;
            }
        }
        return o;
    };
    //判定输入的是否为日期格式
    isDate = function(obj, ins) {
        var type = typeof obj;
        if (type == "string") {
            var result = obj.match(ins.opts.datePatten) != null;
            if (result)
                return Date.parse(obj) != NaN;
            return result;
        } else if (type == "object") {
            return (obj instanceof Date);
        }
    };

    ///获得一个Binder对象，用于绑定
    getBinder = function(bindWay, searchRang, bUseAutoId) {

        var bindFunction = new _binder(bindWay, searchRang);

        if ($.isFunction(bindWay.bindTo)) {
            bindFunction.setter = bindWay.bindTo;
            bindFunction.getter = null;
        }
        else {
            var bindTo = bindWay.bindTo == undefined ? bindWay.dataMember.replace(/\./g, "_") : bindWay.bindTo;
            var tmp = bindWay.bindTool.opts.template;
            var control = findControl(bindTo, searchRang, bUseAutoId, tmp != false ? tmp.attr("id") : undefined);
            if (control.ctrl.size() != 0) {
                bindFunction.target = control.ctrl;
                bindFunction.attr = control.attr;
            }
            else { return null; }
        }

        if ($.isFunction(bindWay.bindFrom)) {
            bindFunction.getter = bindWay.bindFrom;
        }
        else if (bindWay.bindFrom != undefined) {
            var ctrl = findControl(bindFrom, searchRang, bUseAutoId);
            bindFunction.getTarget = ctrl.ctrl;
            bindFunction.getAttr = ctrl.attr;
        }
        return bindFunction;
    };

    //根据exp查询control对象及attributeName，返回 control:control_jquery,attr:attributeName
    findControl = function(exp, searchRang, bUseAutoId, templateId) {
        var result = { ctrl: null, attr: null };
        var dotPos = -1;
        if (typeof exp == "string") {
            dotPos = exp.indexOf(".");
            if (dotPos != 0) { //使用id赋值。
                var id = dotPos == -1 ? exp : exp.substring(0, dotPos);
                //tempalte就是绑定数据的HTML
                result.ctrl = templateId == id ? searchRang : (bUseAutoId ? $("[srcid='" + id + "']", searchRang) : $("#" + id, searchRang));
            }
            else { //相当于 dotPos =1 就是使用 class查找控件
                dotPos = exp.indexOf(".", 1)
                result.ctrl = dotPos == -1 ? $(exp, searchRang) : $(exp.substring(0, dotPos), searchRang);
            }
        }
        else { result.ctrl = $(exp); }
        result.attr = dotPos == -1 ? null : exp.substring(dotPos + 1);
        return result;
    };
    //通过jsonExp表达式从jsonObject设定value中储存值
    setObjVal = function(jObj, jsonExp, value, bindWay) {
        var aryKeys = jsonExp.split(".");
        var currentObject = jObj;
        var lastIndex = aryKeys.length - 1;

        $.each(aryKeys, function(i) {
            if (currentObject[this] == undefined) {
                currentObject[this] = {};
            }
            if (i != lastIndex) {
                currentObject = currentObject[this];
            }
            else {
                currentObject[this] = bindWay.type == "date" ? bindWay.bindTool.opts.formatToDate(value) : s2v(value, bindWay);
            }

        });
    };



    _2BindWay = function(jObj, bindInstance, ary, parentKey) {
        if (ary == undefined) {
            ary = [];
        }
        for (var key in jObj) {
            newKey = parentKey == undefined ? key : parentKey + "." + key;
            var v = jObj[key];
            if (typeof v == "object" && !$.isArray(v) && !(v instanceof Date)) {
                _2BindWay(v, bindInstance, ary, newKey);
            }
            else {
                ary.push({ dataMember: newKey, bindTool: bindInstance });
            }
        }
        return ary;
    };

    // binder;
    _binder = function(bindWay, srang) {
        //this.target，this.attr; 应用属性，自动赋值，无需要理会。
        //this.getTarget, this.getAttr //biner有的属性，通过外部设定获得，因此注释这段代码。
        this.bindWay = bindWay;
        this.setter = _bindAct;
        this.getter = _bindAct;
        this.srang = srang;
        this.setData = function(jsonObject, data) {
            this.setter.call(this.srang, jsonObject, data, this.target, this.attr);
        };
        this.getData = function(jsonObject, jsonExp) {
            var t = this.getTarget == undefined ? this.target : this.getTarget;
            var a = this.getAttr == undefined ? this.attr : this.getAttr;
            var value = this.getter.call(this.srang, jsonObject, undefined, t, a);
            if (value != undefined) {
                setObjVal(jsonObject, jsonExp, value, this.bindWay)
            }
        };
    };
    _bindAct = function(jsonObject, data, target, attributeName) {
        var isGetData = data == undefined;
        switch (target[0].nodeName) {
            case "INPUT":
                return _input(target, attributeName, data);
            case "SELECT":
                return _select(target, data);
            case "TEXTAREA":
                return isGetData ? target.val() : target.val(data);
        }
        if (isGetData) {
            return attributeName != null ? target.attr(attributeName) : target.text();
        }
        else {
            attributeName != null ? target.attr(attributeName, data) : target.text(data);
        }
    };

    // 绑定到select，如果data一个或者data数组并且select上有size="number"，那么就是匹配。
    // 否则就是加入options.
    _select = function(target, data) {
        if (data == undefined) {
            return target.val();
        }
        else {
            var constSelected = "selected";
            target.children().removeAttr(constSelected)
            var v = $.isArray(data) ? $(data) : $.makeArray(data);
            var duplicateValue = [];
            $(v).each(function() {
                var name = this.text != undefined ? this.text : this;
                var sValue = this.value != undefined ? this.value : this;
                if (sValue != "") {
                    var found = $("[value='" + sValue + "']", target).size() != 0;
                    var addToDuplicateValue = this.hasOwnProperty(constSelected) ? this.selected : false;
                    if (!found) {
                        target.append("<option value='" + sValue + "'>" + name + "</option>");
                    }
                    addToDuplicateValue = addToDuplicateValue || found;

                    if (addToDuplicateValue && $.inArray(sValue, duplicateValue) == -1) {
                        duplicateValue.push(sValue + "");
                    }
                }
            });
            target.val(duplicateValue);
        }
    };

    _genId = function(area) {
        return area.selector.replace(/[\s.#]/g, "");
    };

    _input = function(target, attributeName, data) {
        var getData = data == undefined;
        var radioCheckBoxValue = "checked";
        if (attributeName == null) {
            switch (target.attr("type")) {
                case "radio":
                    var val = null;
                    target.each(function() {
                        if (getData && $(this).attr(radioCheckBoxValue)) {
                            val = $(this).val();
                            return false;
                        }
                        else if ($(this).val() == data) {
                            $(this).attr(radioCheckBoxValue, radioCheckBoxValue);
                            return false;
                        }
                    });                    
                    return val;
                case "checkbox":
                    if (getData) {
                        return target[0].checked;
                    }
                    attributeName = radioCheckBoxValue;
                    data = data ? radioCheckBoxValue : "";
                    break;
                default:
                    attributeName = "value";
            }
        }
        if (getData) {
            return target.attr(attributeName);
        }
        else {
            target.attr(attributeName, data);
        }
    };

    s2v = function(strValue, bindWay) {
        var ins = bindWay.bindTool;
        var type = bindWay.type;
        switch (type) {
            case "string":
                return strValue;
            case "number":
                return strValue.indexOf(".") == -1 ? parseInt(strValue) : parseFloat(strValue);
            case "boolean":
                return ins.opts.boolExp[strValue];
            case "date":
                if (strValue.toString().match(ins.opts.datePatten)) {
                    return ins.opts.dateCover(strValue);
                }
                else {
                    var r = Date.parse(strValue);
                    if (!isNaN(r)) {
                        return new Date(r);
                    }
                }
                return new Date(strValue);
            default:
                if (type != undefined && type.indexOf("array") != -1) {
                    if ($.isArray(strValue)) {
                        return strValue;
                    }
                    else {
                        return $.makeArray(strValue);
                    }
                }
                return strValue;
        }
    };
    fireEvent = function(thisPointer, fun) {
        if ($.isFunction(fun)) {
            return fun.apply(thisPointer, [].slice.call(arguments, 2));
        }
        return undefined;
    };

    $.fn.remoteBindTool = function() {
        var args = arguments;
        if (typeof args[0] == "string") {
            var opts = $(this).data("_bt").opts;
            switch (args[0]) {
                case "bind":
                    var sendData = fireEvent(this, opts.onGetting, opts, args[1]);
                    sendData = sendData == undefined ? args[1] : null;
                    var s = this;
                    $.getJSON(opts.getUrl, sendData, function(data) {
                        $(s).bindTool('bind', data);
                    });
                    break;
                case "submit":
                    var data = { data: $(this).bindTool('collect') };
                    var result = fireEvent(this, opts.onSubmitting, data);
                    if (result != undefined) {
                        data = result;
                    }
                    $.post(opts.submitUrl, data, function(returnData) {
                        fireEvent(this, opts.onSubmitted, returnData);
                    });
                    break;
            }
        }
        else {
            var opts = $.extend({}, $.fn.remoteBindTool.defaults, args[0]);
            $(this).bindTool(opts);
        }
        return this;
    };



    $.fn.remoteBindTool.defaults = {
        getUrl: false,  //获得数据的Url      
        onGetting: false, //当remoteBind的时候发生. function(opts){return 发送给getURL的数据};
        submitUrl: false, //数据提交的URL //提交的url
        onSubmitting: false, //提交前数据的处理    function({data:收集后的数据});
        onSubmitted: false //提交后数据处理 function(returnData);
    };

})($);

